/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  public class SelectionRange
  {
    internal SelectionRange()
    {
    }

    public SelectionRange( FilterExpressionBase filterExpression, Predicate<object> filter )
      : this()
    {
      if( ( filterExpression == null ) && ( filter == null ) )
        throw new InvalidOperationException( "An attempt was made to create a selection range while both the filterExpression and filter parameters are null." );

      m_sortDescriptions = new List<SortDescription>();
      this.SortDescriptions = new ReadOnlyCollection<SortDescription>( m_sortDescriptions );

      m_filterExpression = filterExpression;
      m_filter = filter;

      this.StartRangeInfos.FromStart();
      this.EndRangeInfos.ToEnd();

      this.UseDefaultDataRangeContext = false;
    }


    public SelectionRange( IEnumerable<SortDescription> sortDescriptons, FilterExpressionBase filterExpression, Predicate<object> filter )
      : this()
    {
      if( sortDescriptons == null )
        throw new ArgumentNullException( "sortDescriptons" );

      m_sortDescriptions = new List<SortDescription>( sortDescriptons );
      this.SortDescriptions = new ReadOnlyCollection<SortDescription>( m_sortDescriptions );

      //both these properties CAN be null.
      m_filterExpression = filterExpression;
      m_filter = filter;

      this.UseDefaultDataRangeContext = false;
    }

    private void OnRangeChanging( object sender, EventArgs e )
    {
      throw new InvalidOperationException( "An attempt was made to change the StartRangeInfo or EndRangeInfo properties of a SelectionRange that is selecting from the start or until the end, respectively." );
    }

    public static SelectionRange All
    {
      get
      {
        SelectionRange selectAll = new SelectionRange();
        selectAll.StartRangeInfos.FromStart();
        selectAll.EndRangeInfos.ToEnd();

        return selectAll;
      }
    }

    #region SortDescriptions

    public ReadOnlyCollection<SortDescription> SortDescriptions
    {
      get;
      private set;
    }

    private IList<SortDescription> m_sortDescriptions;

    #endregion

    #region FilterExpression Property

    public FilterExpressionBase FilterExpression
    {
      get
      {
        return m_filterExpression;
      }
      private set
      {
        if( value != m_filterExpression )
        {
          m_filterExpression = value;
          m_filterExpressionPredicate = null;
        }
      }
    }

    private FilterExpressionBase m_filterExpression; //null
    private Func<object, bool> m_filterExpressionPredicate; //null

    #endregion

    #region Filter Property

    public Predicate<object> Filter
    {
      get
      {
        return m_filter;
      }
      private set
      {
        if( value != m_filter )
        {
          m_filter = value;
        }
      }
    }

    private Predicate<object> m_filter;

    #endregion

    #region StartRangeInfos Property

    public StartRangeDictionary StartRangeInfos
    {
      get
      {
        return m_startRange;
      }
    }

    private StartRangeDictionary m_startRange = new StartRangeDictionary();

    #endregion StartRangeInfos Property

    #region EndRangeInfos Property

    public EndRangeDictionary EndRangeInfos
    {
      get
      {
        return m_endRange;
      }
    }

    private EndRangeDictionary m_endRange = new EndRangeDictionary();

    #endregion EndRangeInfos Property

    #region IsEmpty Property

    public bool IsEmpty
    {
      get
      {
        if( ( this.SortDescriptions.Count == 0 ) && ( this.StartRangeInfos.Count == 0 ) && ( this.EndRangeInfos.Count == 0 ) && !this.SelectFromStart && !this.SelectToEnd )
          return true;

        return false;
      }
    }

    #endregion IsEmpty Property

    #region IsSingleItem Property

    internal bool IsSingleItem
    {
      get
      {
        foreach( var sortDesc in this.SortDescriptions )
        {
          object startObj;
          object endObj;

          string propName = sortDesc.PropertyName;

          //There need to be a value for start AND end for each and every sort descriptions, otherwise, we can't be sure 
          //it maps to a single item.
          if( !( m_startRange.TryGetValue( propName, out startObj ) && m_endRange.TryGetValue( propName, out endObj ) ) )
            return false;

          //each and every value must match for them to be single item.
          if( !Object.Equals( startObj, endObj ) )
            return false;

        }

        return true;
      }
    }

    #endregion

    #region UseDefaultDataRangeContext Property

    internal bool UseDefaultDataRangeContext
    {
      get
      {
        return m_useDefaultDataRangeContext;
      }
      private set
      {
        if( m_useDefaultDataRangeContext != value )
        {
          m_useDefaultDataRangeContext = value;
        }
      }
    }

    private bool m_useDefaultDataRangeContext = true;

    #endregion

    #region SelectFromStart Property

    internal bool SelectFromStart
    {
      get
      {
        return m_startRange.SelectFromStart;
      }
    }

    #endregion

    #region SelectToEnd

    internal bool SelectToEnd
    {
      get
      {
        return m_endRange.SelectToEnd;
      }
    }

    #endregion

    #region DataRangeContext Property

    internal DataRangeContext DataRangeContext
    {
      get
      {
        return m_dataRangeContext;
      }
      set
      {
        if( value != m_dataRangeContext )
        {
          if( m_dataRangeContext != null )
            throw new InvalidOperationException( "An attempt was made to set a DataRangeContext when one has alrady been provided." );

          m_dataRangeContext = value;

          if( m_dataRangeContext != null )
          {
            m_filter = m_dataRangeContext.Filter;
            m_filterExpression = m_dataRangeContext.FilterExpression;

            m_sortDescriptions = m_dataRangeContext.SortDescriptions;
            this.SortDescriptions = new ReadOnlyCollection<SortDescription>( m_sortDescriptions );
          }
        }
      }
    }

    private DataRangeContext m_dataRangeContext;

    #endregion

    public override bool Equals( object obj )
    {
      if( !( obj is SelectionRange ) )
        return false;

      SelectionRange selectionRange = ( SelectionRange )obj;

      return this.Equals( selectionRange );
    }

    public bool Equals( SelectionRange obj )
    {
      if( obj == null )
        return false;

      // Only one of the compared ranges have SelectFromStart or SelectFromEnd => different SelectionRanges.
      if( ( this.SelectFromStart != obj.SelectFromStart ) || ( this.SelectToEnd != obj.SelectToEnd ) )
        return false;

      // Both have same values for SelectFromStart and SelectFromEnd and they both are true, same SelectionRange.
      if( this.SelectFromStart && this.SelectToEnd )
        return true;

      if( ( this.StartRangeInfos.IsInclusive != obj.StartRangeInfos.IsInclusive ) || ( this.EndRangeInfos.IsInclusive != obj.EndRangeInfos.IsInclusive ) )
        return false;

      int count = this.SortDescriptions.Count;

      if( count != obj.SortDescriptions.Count )
        return false;

      for( int i = 0; i < count; i++ )
      {
        SortDescription sortDesc = this.SortDescriptions[ i ];

        if( !sortDesc.Equals( obj.SortDescriptions[ i ] ) )
          return false;

        try
        {
          // Only compare start value if not from start because StartRangeInfo
          // does not contain any value
          if( !this.SelectFromStart )
          {
            object startValue1 = this.StartRangeInfos[ sortDesc.PropertyName ];
            object startValue2 = obj.StartRangeInfos[ sortDesc.PropertyName ];

            if( !Object.Equals( startValue1, startValue2 ) )
              return false;
          }

          // Only compare end value if not from end because EndRangeInfo
          // does not contain any value
          if( !this.SelectToEnd )
          {
            object endValue1 = this.EndRangeInfos[ sortDesc.PropertyName ];
            object endValue2 = obj.EndRangeInfos[ sortDesc.PropertyName ];

            if( !Object.Equals( endValue1, endValue2 ) )
              return false;
          }
        }
        catch( KeyNotFoundException )
        {
          throw new InvalidOperationException( "The data in the SelectionRange is not consistent." );
        }
      }

      return true;
    }

    public override int GetHashCode()
    {
      int hash = 17;

      foreach( SortDescription sortDesc in this.SortDescriptions )
      {
        hash = hash ^ sortDesc.GetHashCode();

        object outValue;

        if( !this.StartRangeInfos.TryGetValue( sortDesc.PropertyName, out outValue ) )
        {
          throw new InvalidOperationException( "The data in the SelectionRange is not consistent." );
        }
        else
        {
          hash = hash ^ outValue.GetHashCode();
        }

        if( !this.EndRangeInfos.TryGetValue( sortDesc.PropertyName, out outValue ) )
        {
          throw new InvalidOperationException( "The data in the SelectionRange is not consistent." );
        }
        else
        {
          hash = hash ^ outValue.GetHashCode();
        }
      }

      return hash;
    }

    internal bool IsFilteredOut( object item )
    {
      if( this.Filter != null && !this.Filter( item ) )
        return true;

      if( this.FilterExpression != null )
      {
        if( m_filterExpressionPredicate == null )
        {
          PredicateBuilder pb = new PredicateBuilder( this.FilterExpression );

          m_filterExpressionPredicate = pb.ToPredicate( item.GetType() );
        }

        if( !m_filterExpressionPredicate.Invoke( item ) )
          return true;
      }

      return false;
    }

    #region Sub Classes

    public class StartRangeDictionary : NotificationDictionary<string, object>
    {
      public StartRangeDictionary()
      {
        this.IsInclusive = true;
        this.DictionaryChanging += new EventHandler( OnStartRangeDictionaryChanging );
      }

      public bool IsInclusive
      {
        get;
        set;
      }

      internal bool SelectFromStart
      {
        get;
        private set;
      }

      public void FromStart()
      {
        if( !this.SelectFromStart )
        {
          if( this.Count > 0 )
          {
            this.Clear();
          }
          this.SelectFromStart = true;
        }
      }

      private void OnStartRangeDictionaryChanging( object sender, EventArgs e )
      {
        if( this.SelectFromStart )
        {
          this.SelectFromStart = false;
        }
      }
    }

    public class EndRangeDictionary : NotificationDictionary<string, object>
    {
      public EndRangeDictionary()
      {
        this.IsInclusive = true;
        this.DictionaryChanging += new EventHandler( OnEndRangeDictionaryChanging );
      }

      public bool IsInclusive
      {
        get;
        set;
      }

      internal bool SelectToEnd
      {
        get;
        private set;
      }

      public void ToEnd()
      {
        if( !this.SelectToEnd )
        {
          if( this.Count > 0 )
          {
            this.Clear();
          }
          this.SelectToEnd = true;
        }
      }

      private void OnEndRangeDictionaryChanging( object sender, EventArgs e )
      {
        if( this.SelectToEnd )
        {
          this.SelectToEnd = false;
        }
      }
    }

    public class NotificationDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
      public event EventHandler DictionaryChanging;
      public event EventHandler DictionaryChanged;

      #region IDictionary<TKey,TValue> Members

      public void Add( TKey key, TValue value )
      {
        this.OnDictionaryChanging();
        m_wrappedDictionary.Add( key, value );
        this.OnDictionaryChanged();
      }

      public bool ContainsKey( TKey key )
      {
        return m_wrappedDictionary.ContainsKey( key );
      }

      public ICollection<TKey> Keys
      {
        get
        {
          return m_wrappedDictionary.Keys;
        }
      }

      public bool Remove( TKey key )
      {
        this.OnDictionaryChanging();
        bool retval = m_wrappedDictionary.Remove( key );
        this.OnDictionaryChanged();

        return retval;
      }

      public bool TryGetValue( TKey key, out TValue value )
      {
        return m_wrappedDictionary.TryGetValue( key, out value );
      }

      public ICollection<TValue> Values
      {
        get
        {
          return m_wrappedDictionary.Values;
        }
      }

      public TValue this[ TKey key ]
      {
        get
        {
          return m_wrappedDictionary[ key ];
        }
        set
        {
          this.OnDictionaryChanging();
          m_wrappedDictionary[ key ] = value;
          this.OnDictionaryChanged();
        }
      }

      #endregion

      #region ICollection<KeyValuePair<TKey,TValue>> Members

      public void Add( KeyValuePair<TKey, TValue> item )
      {
        this.OnDictionaryChanging();
        ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Add( item );
        this.OnDictionaryChanged();
      }

      public void Clear()
      {
        this.OnDictionaryChanging();
        this.Clear();
        this.OnDictionaryChanged();
      }

      public bool Contains( KeyValuePair<TKey, TValue> item )
      {
        return ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Contains( item );
      }

      public void CopyTo( KeyValuePair<TKey, TValue>[] array, int arrayIndex )
      {
        ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).CopyTo( array, arrayIndex );
      }

      public int Count
      {
        get
        {
          return m_wrappedDictionary.Count;
        }
      }

      public bool IsReadOnly
      {
        get
        {
          return ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).IsReadOnly;
        }
      }

      public bool Remove( KeyValuePair<TKey, TValue> item )
      {
        this.OnDictionaryChanging();

        bool retval = ( ( ICollection<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).Remove( item );

        this.OnDictionaryChanged();

        return retval;
      }

      #endregion

      #region IEnumerable<KeyValuePair<TKey,TValue>> Members

      public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
      {
        return ( ( IEnumerable<KeyValuePair<TKey, TValue>> )m_wrappedDictionary ).GetEnumerator();
      }

      #endregion

      #region IEnumerable Members

      IEnumerator IEnumerable.GetEnumerator()
      {
        return ( ( IEnumerable )m_wrappedDictionary ).GetEnumerator();
      }

      #endregion

      protected void OnDictionaryChanging()
      {
        if( this.DictionaryChanging != null )
        {
          this.DictionaryChanging( this, EventArgs.Empty );
        }
      }

      protected void OnDictionaryChanged()
      {
        if( this.DictionaryChanged != null )
        {
          this.DictionaryChanged( this, EventArgs.Empty );
        }
      }

      private Dictionary<TKey, TValue> m_wrappedDictionary = new Dictionary<TKey, TValue>();
    }

    #endregion
  }
}
